﻿using System.Collections.Generic;
using UnityEngine;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        /// <summary>
        /// sub-mesh indices. a per vertex array that corresponds to a mesh's sub-meshes.
        /// </summary>
        /// <param name="mesh"></param>
        /// <returns>returns a empty array if mesh is null</returns>
        public static int[] GetSubMeshIndices(this Mesh mesh)
        {
            if (!mesh) return new int[0];
            if (mesh.subMeshCount == 1) return new int[mesh.vertices.Length];

            int[] subMeshIndex = new int[mesh.vertices.Length];
            for (int i = 0; i < mesh.subMeshCount; i++)
            {
                int[] tris = mesh.GetTriangles(i);
                for (int ii = 0; ii < tris.Length; ii++)
                {
                    subMeshIndex[tris[ii]] = i;
                }
            }
            return subMeshIndex;
        }

        /// <summary>
        /// sub-mesh indices. a per vertex array that corresponds to a mesh's sub-meshes.
        /// optional materials: merge indices with matching materials including nulls.
        /// </summary>
        /// <param name="mesh"></param>
        /// <param name="materials"></param>
        /// <returns>returns a empty array if mesh is null</returns>
        public static int[] GetSubMeshIndices(this Mesh mesh, Material[] materials)
        {
            if (!mesh) return new int[0];
            if (mesh.subMeshCount == 1) return new int[mesh.vertices.Length];
            Material mat = null;
            List<Material> subMeshMats = new List<Material>();
            int[] subMeshIDs = new int[mesh.subMeshCount];
            for (int i = 0; i < mesh.subMeshCount; i++)
            {
                if (i < materials.Length)
                    mat = materials[i];
                subMeshIDs[i] = i;
                if (subMeshMats.Contains(mat))
                {
                    subMeshIDs[i] = subMeshMats.IndexOf(mat);
                }
                else subMeshMats.Add(mat);
            }

            int[] meshIndex = new int[mesh.vertices.Length];
            for (int i = 0; i < mesh.subMeshCount; i++)
            {
                int[] tris = mesh.GetTriangles(i);
                for (int ii = 0; ii < tris.Length; ii++)
                {
                    meshIndex[tris[ii]] = subMeshIDs[i];
                }
            }

            return meshIndex;
        }

        public static Mesh GetSubmesh(this Mesh source, int index, Blendshape.CopySettings settings = null)
        {
            var indices = new int[1] { index };
            return GetSubmesh(source, indices, settings);
        }

        public static Mesh GetSubmesh(this Mesh source, int[] subMeshes, Blendshape.CopySettings settings = null)
        {
            if (!source || IsNullOrEmpty(subMeshes)) return null;

            MeshVertexData sourceData = new MeshVertexData(source);
            MeshVertexData targetData = new MeshVertexData();
            Dictionary<int, int> map = new Dictionary<int, int>();
            List<int> newIndices = new List<int>();
            List<int> orignalIndex = new List<int>();

            int subMeshCount = 0;
            for (int i = 0; i < subMeshes.Length; i++)
            {
                int index = subMeshes[i];
                if (index < 0 || index >= source.subMeshCount) continue;
                subMeshCount++;
                int[] indices = source.GetTriangles(index);
                for (int ii = 0; ii < indices.Length; ii++)
                {
                    int currentIndex = indices[ii];
                    int newIndex;
                    if (!map.TryGetValue(currentIndex, out newIndex))
                    {
                        newIndex = targetData.AddVertexData(sourceData, currentIndex);
                        map.Add(currentIndex, newIndex);
                        orignalIndex.Add(currentIndex);
                    }
                    newIndices.Add(newIndex);
                }
            }

            if (subMeshCount == 0) { Debug.LogError("no sub-meshes found!");  return null; }

            Mesh newMesh = targetData.GetMesh(newIndices.ToArray(), source.bindposes);
            newMesh.name = string.IsNullOrEmpty(source.name) ? "" : source.name;
            for (int i = 0; i < subMeshes.Length; i++) { newMesh.name += "_" + subMeshes[i].ToString(); }

            newMesh.CopyBlendshapes(source, orignalIndex.ToArray(), settings);

            newMesh.RecalculateBounds();
            return newMesh;
        }

        public static Mesh GetSubmesh(this Mesh source, int index, out int[] vertexMap)
        {
            vertexMap = new int[0];
            if (!source) return null;
            if (index < 0 || index >= source.subMeshCount) { Debug.LogError("no sub-meshes found!"); return null; }

            var sourceData = new MeshVertexData(source);
            var targetData = new MeshVertexData();
            var map = new Dictionary<int, int>();
            var newIndices = new List<int>();
            var orignalIndex = new List<int>();
            var indices = source.GetTriangles(index);

            for (int ii = 0; ii < indices.Length; ii++)
            {
                int currentIndex = indices[ii];
                int newIndex;
                if (!map.TryGetValue(currentIndex, out newIndex))
                {
                    newIndex = targetData.AddVertexData(sourceData, currentIndex);
                    map.Add(currentIndex, newIndex);
                    orignalIndex.Add(currentIndex);
                }
                newIndices.Add(newIndex);
            }

            var newMesh = targetData.GetMesh(newIndices.ToArray(), source.bindposes);
            newMesh.name = string.IsNullOrEmpty(source.name) ? "" : source.name;
            newMesh.name += "_" + index.ToString();
            newMesh.RecalculateBounds();
            vertexMap = orignalIndex.ToArray();
            return newMesh;
        }

        public static List<Renderer> MeshRendererSplit(Renderer context)
        {
            if (IsNullOrNotInAScene(context)) return new List<Renderer>();
            if (IsNullOrNotVailid(context)) return new List<Renderer>();

            var active = context.gameObject.activeSelf;
            context.gameObject.SetActive(true);

            var mesh = context.GetSharedMesh();
            var createdObjects = new List<Renderer>();
            var materials = context.GetSharedMaterialsFromSubMeshes();
            for (int i = 0; i < mesh.subMeshCount; i++)
            {
                var renderer = context.CloneAsMeshRenderer() as MeshRenderer;
                var name = context.name + " " + i.ToString();

                renderer.gameObject.name = name;
                var newMesh = mesh.GetSubmesh(i);
                renderer.SetSharedMesh(newMesh);

                if (!materials[i]) renderer.sharedMaterials = new Material[1] { null };
                else
                {
                    newMesh.name = materials[i].name;
                    renderer.gameObject.name = context.name + " " + materials[i].name;
                    renderer.sharedMaterials = new Material[1] { materials[i] };
                }
                createdObjects.Add(renderer);
                renderer.gameObject.SetActive(true);
            }

            context.gameObject.SetActive(active);
            return createdObjects;
        }

        public static List<Renderer> SkinnedMeshSplit(SkinnedMeshRenderer context)
        {
            if (IsNullOrNotInAScene(context)) return new List<Renderer>();
            if (!HasMinimumRequirements(context, true)) return new List<Renderer>();

            var active = context.gameObject.activeSelf;
            context.gameObject.SetActive(true);

            var tempSMR = context.Clone() as SkinnedMeshRenderer;
            var mesh = tempSMR.sharedMesh;
            var createdObjects = new List<Renderer>();
            var materials = context.GetSharedMaterialsFromSubMeshes();
            for (int i = 0; i < mesh.subMeshCount; i++)
            {
                SkinnedMeshRenderer smr = tempSMR.Clone(true) as SkinnedMeshRenderer;
                smr.gameObject.SetActive(true);
                smr.transform.SetParent(context.transform.parent);
                string name = tempSMR.name + " " + i.ToString();
                smr.name = name;

                Mesh newMesh = mesh.GetSubmesh(new int[1] { i });
                newMesh.name = name;
                smr.rootBone = tempSMR.rootBone ? tempSMR.rootBone : tempSMR.bones.FindMinHierarchicalOrder();
                smr.bones = tempSMR.GetBones();
                smr.sharedMesh = newMesh;
                smr.SetBounds();

                if (materials[i])
                {
                    newMesh.name = tempSMR.name + " " + smr.sharedMaterials[i].name;
                    smr.gameObject.name = context.name + " " + smr.sharedMaterials[i].name;
                    smr.sharedMaterials = new Material[1] { smr.sharedMaterials[i] };
                }
                else smr.sharedMaterials = new Material[1] { null };

                smr.gameObject.SetActive(true);
                createdObjects.Add(smr);
            }

            context.gameObject.SetActive(active);

            Release(tempSMR.gameObject);
            Release(mesh);
            return createdObjects;
        }
    }
}